/**
 * @fileOverview The "indent" plugin. Increse and decrease indent commands.
 */
(function() {
   var listNodeNames = { ol:1,ul:1 },
      isNotWhitespaces = VED.dom.walker.whitespaces( true ),
      isNotBookmark = VED.dom.walker.bookmark( false, true );

   var indentCommand = new Class({
      initialize: function( editor, name ) {
         this.name = name;
         this.useIndentClasses = editor.config.indentClasses && editor.config.indentClasses.length > 0;
         if ( this.useIndentClasses ) {
            this.classNameRegex = new RegExp( '(?:^|\\s+)(' + editor.config.indentClasses.join( '|' ) + ')(?=$|\\s)' );
            this.indentClassMap = {};
            for ( var i = 0; i < editor.config.indentClasses.length; i++ )
               this.indentClassMap[ editor.config.indentClasses[ i ] ] = i + 1;
         }

         this.startDisabled = name == 'outdent';
      }
   });

   // Returns the CSS property to be used for identing a given element.
   function getIndentCssProperty( element, dir ) {
      return ( dir || element.getComputedStyle( 'direction' ) ) == 'ltr' ? 'margin-left' : 'margin-right';
   }

   function isListItem( node ) {
      return node.type == VED.NODE_ELEMENT && node.is( 'li' );
   }

   indentCommand.implement({
      // It applies to a "block-like" context.
      context: 'p',
      refresh: function( editor, path ) {
         var list = path && path.contains( listNodeNames ),
            firstBlock = path.block || path.blockLimit;

         if ( list )
            this.setState( VED.TRISTATE_OFF );

         else if ( !this.useIndentClasses && this.name == 'indent' )
            this.setState( VED.TRISTATE_OFF );

         else if ( !firstBlock )
            this.setState( VED.TRISTATE_DISABLED );

         else if ( this.useIndentClasses ) {
            var indentClass = firstBlock.$.className.match( this.classNameRegex ),
               indentStep = 0;

            if ( indentClass ) {
               indentClass = indentClass[ 1 ];
               indentStep = this.indentClassMap[ indentClass ];
            }

            if ( ( this.name == 'outdent' && !indentStep ) || ( this.name == 'indent' && indentStep == editor.config.indentClasses.length ) )
               this.setState( VED.TRISTATE_DISABLED );
            else
               this.setState( VED.TRISTATE_OFF );
         } else {
            var indent = parseInt( firstBlock.getStyle( getIndentCssProperty( firstBlock ) ), 10 );
            if ( isNaN( indent ) )
               indent = 0;
            if ( indent <= 0 )
               this.setState( VED.TRISTATE_DISABLED );
            else
               this.setState( VED.TRISTATE_OFF );
         }
      },
      exec: function( editor ) {
         var self = this,
            database = {};

         function indentList( listNode ) {
            // Our starting and ending points of the range might be inside some blocks under a list item...
            // So before playing with the iterator, we need to expand the block to include the list items.
            var startContainer = range.startContainer,
               endContainer = range.endContainer;
            while ( startContainer && !startContainer.getParent().equals( listNode ) )
               startContainer = startContainer.getParent();
            while ( endContainer && !endContainer.getParent().equals( listNode ) )
               endContainer = endContainer.getParent();

            if ( !startContainer || !endContainer )
               return;

            // Now we can iterate over the individual items on the same tree depth.
            var block = startContainer,
               itemsToMove = [],
               stopFlag = false;
            while ( !stopFlag ) {
               if ( block.equals( endContainer ) )
                  stopFlag = true;
               itemsToMove.push( block );
               block = block.getNext();
            }
            if ( itemsToMove.length < 1 )
               return;

            // Do indent or outdent operations on the array model of the list, not the
            // list's DOM tree itself. The array model demands that it knows as much as
            // possible about the surrounding lists, we need to feed it the further
            // ancestor node that is still a list.
            var listParents = listNode.getParents( true );
            for ( var i = 0; i < listParents.length; i++ ) {
               if ( listParents[ i ].getName && listNodeNames[ listParents[ i ].getName() ] ) {
                  listNode = listParents[ i ];
                  break;
               }
            }
            var indentOffset = self.name == 'indent' ? 1 : -1,
               startItem = itemsToMove[ 0 ],
               lastItem = itemsToMove[ itemsToMove.length - 1 ];

            // Convert the list DOM tree into a one dimensional array.
            var listArray = VED.plugins.list.listToArray( listNode, database );

            // Apply indenting or outdenting on the array.
            var baseIndent = listArray[ lastItem.getCustomData( 'listarray_index' ) ].indent;
            for ( i = startItem.getCustomData( 'listarray_index' ); i <= lastItem.getCustomData( 'listarray_index' ); i++ ) {
               listArray[ i ].indent += indentOffset;
               // Make sure the newly created sublist get a brand-new element of the same type. (#5372)
               if ( indentOffset > 0 ) {
                  var listRoot = listArray[ i ].parent;
                  listArray[ i ].parent = new VED.dom.element( listRoot.getName(), listRoot.getDocument() );
               }
            }

            for ( i = lastItem.getCustomData( 'listarray_index' ) + 1;
            i < listArray.length && listArray[ i ].indent > baseIndent; i++ )
               listArray[ i ].indent += indentOffset;

            // Convert the array back to a DOM forest (yes we might have a few subtrees now).
            // And replace the old list with the new forest.
            var newList = VED.plugins.list.arrayToList( listArray, database, null, editor.config.enterMode, listNode.getDirection() );

            // Avoid nested <li> after outdent even they're visually same,
            // recording them for later refactoring.(#3982)
            if ( self.name == 'outdent' ) {
               var parentLiElement;
               if ( ( parentLiElement = listNode.getParent() ) && parentLiElement.is( 'li' ) ) {
                  var children = newList.listNode.getChildren(),
                     pendingLis = [],
                     count = children.count(),
                     child;

                  for ( i = count - 1; i >= 0; i-- ) {
                     if ( ( child = children.getItem( i ) ) && child.is && child.is( 'li' ) )
                        pendingLis.push( child );
                  }
               }
            }

            if ( newList )
               newList.listNode.replace( listNode );

            // Move the nested <li> to be appeared after the parent.
            if ( pendingLis && pendingLis.length ) {
               for ( i = 0; i < pendingLis.length; i++ ) {
                  var li = pendingLis[ i ],
                     followingList = li;

                  // Nest preceding <ul>/<ol> inside current <li> if any.
                  while ( ( followingList = followingList.getNext() ) && followingList.is && followingList.getName() in listNodeNames ) {
                     // IE requires a filler NBSP for nested list inside empty list item,
                     // otherwise the list item will be inaccessiable. (#4476)
                     if ( VED.env.ie && !li.getFirst( function( node ) {
                        return isNotWhitespaces( node ) && isNotBookmark( node );
                     }))
                        li.append( range.document.createText( '\u00a0' ) );

                     li.append( followingList );
                  }

                  li.insertAfter( parentLiElement );
               }
            }
         }

         function indentBlock() {
            var iterator = range.createIterator(),
               enterMode = editor.config.enterMode;
            iterator.enforceRealBlocks = true;
            iterator.enlargeBr = enterMode != VED.ENTER_BR;
            var block;
            while ( ( block = iterator.getNextParagraph( enterMode == VED.ENTER_P ? 'p' : 'div' ) ) )
               indentElement( block );
         }

         function indentElement( element, dir ) {
            if ( element.getCustomData( 'indent_processed' ) )
               return false;

            if ( self.useIndentClasses ) {
               // Transform current class name to indent step index.
               var indentClass = element.$.className.match( self.classNameRegex ),
                  indentStep = 0;
               if ( indentClass ) {
                  indentClass = indentClass[ 1 ];
                  indentStep = self.indentClassMap[ indentClass ];
               }

               // Operate on indent step index, transform indent step index back to class
               // name.
               if ( self.name == 'outdent' )
                  indentStep--;
               else
                  indentStep++;

               if ( indentStep < 0 )
                  return false;

               indentStep = Math.min( indentStep, editor.config.indentClasses.length );
               indentStep = Math.max( indentStep, 0 );
               element.$.className = element.$.className.replace( self.classNameRegex, '' ).ltrim();
               if ( indentStep > 0 )
                  element.addClass( editor.config.indentClasses[ indentStep - 1 ] );
            } else {
               var indentCssProperty = getIndentCssProperty( element, dir ),
                  currentOffset = parseInt( element.getStyle( indentCssProperty ), 10 );
               if ( isNaN( currentOffset ) )
                  currentOffset = 0;
               var indentOffset = editor.config.indentOffset || 40;
               currentOffset += ( self.name == 'indent' ? 1 : -1 ) * indentOffset;

               if ( currentOffset < 0 )
                  return false;

               currentOffset = Math.max( currentOffset, 0 );
               currentOffset = Math.ceil( currentOffset / indentOffset ) * indentOffset;
               element.setStyle( indentCssProperty, currentOffset ? currentOffset + ( editor.config.indentUnit || 'px' ) : '' );
               if ( element.getAttribute( 'style' ) === '' )
                  element.removeAttribute( 'style' );
            }

            VED.dom.element.setMarker( database, element, 'indent_processed', 1 );
            return true;
         }

         var selection = editor.getSelection(),
            bookmarks = selection.createBookmarks( 1 ),
            ranges = selection && selection.getRanges( 1 ),
            range;


         var iterator = ranges.createIterator();
         while ( ( range = iterator.getNextRange() ) ) {
            var rangeRoot = range.getCommonAncestor(),
               nearestListBlock = rangeRoot;

            while ( nearestListBlock && !( nearestListBlock.type == VED.NODE_ELEMENT && listNodeNames[ nearestListBlock.getName() ] ) )
               nearestListBlock = nearestListBlock.getParent();

            // Avoid having selection enclose the entire list. (#6138)
            // [<ul><li>...</li></ul>] =><ul><li>[...]</li></ul>
            if ( !nearestListBlock ) {
               var selectedNode = range.getEnclosedNode();
               if ( selectedNode && selectedNode.type == VED.NODE_ELEMENT && selectedNode.getName() in listNodeNames ) {
                  range.setStartAt( selectedNode, VED.POSITION_AFTER_START );
                  range.setEndAt( selectedNode, VED.POSITION_BEFORE_END );
                  nearestListBlock = selectedNode;
               }
            }

            // Avoid selection anchors under list root.
            // <ul>[<li>...</li>]</ul> =>   <ul><li>[...]</li></ul>
            if ( nearestListBlock && range.startContainer.type == VED.NODE_ELEMENT && range.startContainer.getName() in listNodeNames ) {
               var walker = new VED.dom.walker( range );
               walker.evaluator = isListItem;
               range.startContainer = walker.next();
            }

            if ( nearestListBlock && range.endContainer.type == VED.NODE_ELEMENT && range.endContainer.getName() in listNodeNames ) {
               walker = new VED.dom.walker( range );
               walker.evaluator = isListItem;
               range.endContainer = walker.previous();
            }

            if ( nearestListBlock ) {
               var firstListItem = nearestListBlock.getFirst( isListItem ),
                  hasMultipleItems = !!firstListItem.getNext( isListItem ),
                  rangeStart = range.startContainer,
                  indentWholeList = firstListItem.equals( rangeStart ) || firstListItem.contains( rangeStart );

               // Indent the entire list if cursor is inside the first list item. (#3893)
               // Only do that for indenting or when using indent classes or when there is something to outdent. (#6141)
               if ( !( indentWholeList && ( self.name == 'indent' || self.useIndentClasses || parseInt( nearestListBlock.getStyle( getIndentCssProperty( nearestListBlock ) ), 10 ) ) && indentElement( nearestListBlock, !hasMultipleItems && firstListItem.getDirection() ) ) )
                  indentList( nearestListBlock );
            } else
               indentBlock();
         }

         // Clean up the markers.
         VED.dom.element.clearAllMarkers( database );

         editor.forceNextSelectionCheck();
         selection.selectBookmarks( bookmarks );
      }
   });

   VED.plugins.add( 'indent', {
      // TODO: Remove this dependency.
      requires: 'list',
      lang: 'en,ru', // %REMOVE_LINE_CORE%
      icons: 'indent,indent-rtl,outdent,outdent-rtl', // %REMOVE_LINE_CORE%
      onLoad: function() {
         // [IE6/7] Raw lists are using margin instead of padding for visual indentation in wysiwyg mode. (#3893)
         if ( VED.env.ie6Compat || VED.env.ie7Compat ) {
            VED.addCss( ".ved_editable ul," +
               ".ved_editable ol" +
               "{" +
               "   margin-left: 0px;" +
               "   padding-left: 40px;" +
               "}" );
         }
      },
      init: function( editor ) {
         if ( editor.blockless )
            return;

         // Register commands.
         var indent = editor.addCommand( 'indent', new indentCommand( editor, 'indent' ) ),
            outdent = editor.addCommand( 'outdent', new indentCommand( editor, 'outdent' ) );

         if ( editor.ui.addButton ) {
            // Register the toolbar buttons.
            editor.ui.addButton( 'Indent', {
               label: editor.lang.indent.indent,
               command: 'indent',
               directional: true,
               toolbar: 'indent,20'
            });
            editor.ui.addButton( 'Outdent', {
               label: editor.lang.indent.outdent,
               command: 'outdent',
               directional: true,
               toolbar: 'indent,10'
            });
         }

         // Register dirChanged listener.
         editor.on( 'dirChanged', function( e ) {
            var range = editor.createRange();
            range.setStartBefore( e.data.node );
            range.setEndAfter( e.data.node );

            var walker = new VED.dom.walker( range ),
               node;

            while ( ( node = walker.next() ) ) {
               if ( node.type == VED.NODE_ELEMENT ) {
                  // A child with the defined dir is to be ignored.
                  if ( !node.equals( e.data.node ) && node.getDirection() ) {
                     range.setStartAfter( node );
                     walker = new VED.dom.walker( range );
                     continue;
                  }

                  // Switch alignment classes.
                  var classes = editor.config.indentClasses;
                  if ( classes ) {
                     var suffix = ( e.data.dir == 'ltr' ) ? [ '_rtl', '' ] : [ '', '_rtl' ];
                     for ( var i = 0; i < classes.length; i++ ) {
                        if ( node.hasClass( classes[ i ] + suffix[ 0 ] ) ) {
                           node.removeClass( classes[ i ] + suffix[ 0 ] );
                           node.addClass( classes[ i ] + suffix[ 1 ] );
                        }
                     }
                  }

                  // Switch the margins.
                  var marginLeft = node.getStyle( 'margin-right' ),
                     marginRight = node.getStyle( 'margin-left' );

                  marginLeft ? node.setStyle( 'margin-left', marginLeft ) : node.removeStyle( 'margin-left' );
                  marginRight ? node.setStyle( 'margin-right', marginRight ) : node.removeStyle( 'margin-right' );
               }
            }
         });
      }
   });
})();






